package cartagows.wsframework.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.Vector;

import javax.security.auth.callback.CallbackHandler;
import javax.xml.namespace.QName;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPHeader;
import org.apache.axiom.soap.SOAPHeaderBlock;
import org.apache.axis2.saaj.util.SAAJUtil;
import org.apache.ws.security.SOAPConstants;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSSecurityEngine;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.components.crypto.Crypto;
import org.apache.ws.security.components.crypto.CryptoFactory;
import org.apache.ws.security.util.WSSecurityUtil;
import org.w3c.dom.Document;

import cartago.Artifact;
import cartago.GUARD;
import cartago.LINK;
import cartago.OPERATION;
import cartago.OpFeedbackParam;
import cartagows.WSMsgInfo;
import cartagows.util.SOAPUtil;
import cartagows.util.XMLib;
import cartagows.wsframework.wsatomictransaction.WSAtomicTransactionOperation;
import cartagows.wsframework.wsbusinessactivities.WSBusinessActivitiesOperation;
import cartagows.wsframework.wscoordination.WSCoordinationMessageElem;
import cartagows.wsframework.wssecurity.core.MessageData;
import cartagows.wsframework.wssecurity.core.PolicyBasedResultsValidator;
import cartagows.wsframework.wssecurity.core.SecurityException;
import cartagows.wsframework.wssecurity.core.SecurityInfo;
import cartagows.wsframework.wssecurity.core.ValidationResults;
import cartagows.wsframework.wssecurity.core.ValidatorData;
import cartagows.wsframework.wssecurity.core.WSSecurityOperation;
import cartagows.wsframework.wssecurity.core.WSSecurityOperationContent;
import cartagows.wsframework.wssecurity.policy.PolicyData;

public class AgentWallet extends Artifact {

	//<EPR, <specificationID, <entryID,specificationInfoEnty> > >
	private HashMap<String, HashMap<Integer, HashMap<String, DataContainer>>> wallet;

	@OPERATION void init(){
		wallet = new HashMap<String, HashMap<Integer,HashMap<String, DataContainer>>>();
	}

	/**
	 * Operation used for add a info in the wallet for the specified endpoint and specification
	 * @param endpoint
	 * @param specificationID
	 * @param walletInfo
	 */
	@OPERATION(guard="checkSpecificationID") public void addInfoToWallet(String endpoint, int specificationID, DataContainer walletInfo, OpFeedbackParam<String> infoID){

		//Check if some informations about the specified endpoint are already present in the wallet
		if(!wallet.containsKey(endpoint)){
			wallet.put(endpoint, new HashMap<Integer, HashMap<String, DataContainer>>());
			wallet.get(endpoint).put(specificationID, new HashMap<String, DataContainer>());
		}
		//Check if informations about the specified endpoint and specification are already present in the wallet
		else if (!wallet.get(endpoint).containsKey(specificationID))
			wallet.get(endpoint).put(specificationID, new HashMap<String, DataContainer>());

		//Generation of the information ID
		String id = UUID.randomUUID().toString();
		infoID.set(id);
		//Information added to the wallet
		wallet.get(endpoint).get(specificationID).put(id,walletInfo);
	}


	/**
	 * Operation used for the retrieval of a specific information stored in the wallet 
	 * @param endpoint
	 * @param specificationID
	 * @ param infoID
	 */
	@LINK (guard="checkParamValidity_getWalletInfo") public void getWalletInfo(String endpoint, Integer specificationID, String infoID, OpFeedbackParam<DataContainer> res){
		res.set(wallet.get(endpoint).get(specificationID).get(infoID));
	}

	/**
	 * Operation used for the retrieval of all the information related to a endpoint and a specificationID
	 * stored into the wallet 
	 * @param endpoint
	 * @param specificationID
	 */
	@LINK (guard="checkParamValidity_getWalletInfo") public void getWalletInfo(String endpoint, Integer specificationID, OpFeedbackParam<HashMap<String, DataContainer>> res){
		res.set(wallet.get(endpoint).get(specificationID));
	}

	/**
	 * Operation used for the validation of a WSMsgInfo with the information contained in the wallet
	 * @param endpoint
	 * @param msg
	 * @param operation
	 * @param res
	 */
	@LINK (guard="checkParamValidity_validateMessage") public void validateMessage(String endpoint, WSMsgInfo msg, IOperation operation, OpFeedbackParam<? super Object> res){
		//execInternalOp("validateMessage", endpoint, msg, operation, null, res);
		this.validateMessage(endpoint, msg, operation, null, res);
	}


	/**
	 * Operation used for the validation of a WSMsgInfo with the information contained in the wallet
	 * @param endpoint
	 * @param msg
	 * @param opData
	 * @param operation
	 */
	@LINK @OPERATION(guard="checkParamValidity_validateMessage") public void validateMessage(String endpoint, WSMsgInfo msg, IOperation operation, DataContainer opData, OpFeedbackParam<? super Object> res){

		boolean isValidationSucceeded = false;
		try{
			if(wallet.containsKey(endpoint)){

				if((operation instanceof WSAtomicTransactionOperation) && (wallet.get(endpoint).containsKey(IOperation.WS_ATOMIC_TRANSACTION))){

					switch (operation.getOperationID()){

					//Coordination context validation
					case WSAtomicTransactionOperation.VALIDATE_AT_CONTEXT:
						OMElement header = XMLib.getInstance().buildElementFromString(msg.getHeader());
						String coordContext = XMLib.getInstance().getElement(WSCoordinationMessageElem.COORDINATION_CONTEXT, header).toString();
						HashMap<String,DataContainer> endpointData = wallet.get(endpoint).get(IOperation.WS_ATOMIC_TRANSACTION);
						for(Entry<String,DataContainer> data: endpointData.entrySet()){
							if(((OMElement)data.getValue().getData()).toString().equals(coordContext)){
								isValidationSucceeded = true;
								res.set(new Boolean(true));
								break;
							}
							else
								continue;
						}
						break;
					}
				}
				else if((operation instanceof WSBusinessActivitiesOperation) && (wallet.get(endpoint).containsKey(IOperation.WS_BUSINESS_ACTIVITIES))){

				}
				else if((operation instanceof WSSecurityOperation) && (wallet.get(endpoint).containsKey(IOperation.WS_SECURITY))){

					switch (operation.getOperationID()){

					//Coordination context validation
					case WSSecurityOperation.VALIDATE_MSG:
						Document doc = null;
						try {
							doc = SOAPUtil.toSOAPPart(msg.getEnvelope());
						} catch (Exception e2) {
							// TODO Auto-generated catch block
							e2.printStackTrace();
						}


						HashMap<String, DataContainer> resSecurity = wallet.get(endpoint).get(IOperation.WS_SECURITY);
						Set list = resSecurity.keySet();
						SecurityInfo securityInfo = null;
						for(Object key:list){
							DataContainer value = resSecurity.get(key);
							securityInfo = (SecurityInfo) value.getData();
							break;
						}


						WSSecurityOperationContent op = (WSSecurityOperationContent) opData.getData();
						PolicyData pd = op.getPolicyData();

						MessageData md = new MessageData(pd,doc,null,securityInfo,null,op.isInitiator());

						WSSecurityEngine secEngine = new WSSecurityEngine();

						ValidatorData data = new ValidatorData(md);

						SOAPEnvelope env = SAAJUtil.getSOAPEnvelopeFromDOOMDocument(doc);
						SOAPHeader header = env.getHeader();
						if(header == null) {
							throw new SecurityException("missingSOAPHeader");
						}

						ArrayList headerBlocks = header.getHeaderBlocksWithNSURI(WSConstants.WSSE_NS);
						SOAPHeaderBlock secHeader = null;
						if(headerBlocks != null) {
							Iterator headerBlocksIterator = headerBlocks.iterator();
							while (headerBlocksIterator.hasNext()) {
								SOAPHeaderBlock elem = (SOAPHeaderBlock) headerBlocksIterator.next();
								if(elem.getLocalName().equals(WSConstants.WSSE_LN)) {
									secHeader = elem;
									break;
								}
							}
						}

						if(secHeader == null) {
							throw new SecurityException("missingSecurityHeader");
						}
						SOAPConstants soapConstants = WSSecurityUtil.getSOAPConstants(doc.getDocumentElement());
						String actorValue = secHeader.getAttributeValue(new QName(soapConstants.getEnvelopeURI(), "actor"));

						Crypto cryptoDec = CryptoFactory.getInstance(securityInfo.getCryptoDecProperties());
						Crypto cryptoEnc = CryptoFactory.getInstance(securityInfo.getCryptoEncProperties());
						CallbackHandler cb = securityInfo.getPwdCallback();
						Vector wsResult = null;
						try {
							wsResult = secEngine.processSecurityHeader(doc, actorValue, cb, cryptoEnc,cryptoDec);
						} catch (WSSecurityException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}

						PolicyBasedResultsValidator validator = new PolicyBasedResultsValidator();
						validator.validate(data, wsResult);

						SOAPEnvelope envelope = SAAJUtil.getSOAPEnvelopeFromDOOMDocument(doc);
						SOAPBody body = envelope.getBody();
						SOAPHeader head = envelope.getHeader();
						msg.setEnvelope(envelope.toString());
						msg.setHeader(head.toString());
						msg.setBody(body.getFirstElement().toString());
						msg.setMsgContent(body.getFirstElement().getFirstElement().toString());

						isValidationSucceeded = true;

						ValidationResults validationRes = new ValidationResults(msg, wsResult);
						res.set(validationRes);

						break;

					}
				}

			}
		}
		catch(Exception ex){
			failed("validation_failed");
		}
		if(!isValidationSucceeded)
			failed("validation_failed");
	}

	/**
	 * Guard that checks if the parameters specified when the "addInfoToWallet" operation is invoked are valid or not
	 * @param endpoint
	 * @param specificationID
	 * @param walletInfo
	 * @return
	 */
	@GUARD public boolean checkSpecificationID(String endpoint, int specificationID, DataContainer walletInfo, OpFeedbackParam<String> infoID){
		return FrameworkIDValidator.getInstance().validateSpecification(specificationID);
	}

	/**
	 * Guard that checks if the parameters specified when the "getWalletInfo" operation is invoked are valid or not
	 * @param endpoint
	 * @param specificationID
	 * @return
	 */
	@GUARD public boolean checkParamValidity_getWalletInfo(String endpoint, Integer specificationID, OpFeedbackParam<HashMap<String, DataContainer>> res){
		return (wallet.containsKey(endpoint) && wallet.get(endpoint).containsKey(specificationID));
	}

	/**
	 * Guard that checks if the parameters specified when the "getWalletInfo" operation is invoked are valid or not
	 * @param endpoint
	 * @param specificationID
	 * @param infoID
	 * @return
	 */
	@GUARD public boolean checkParamValidity_getWalletInfo(String endpoint, Integer specificationID, String infoID, OpFeedbackParam<DataContainer> res){
		return (wallet.containsKey(endpoint) && wallet.get(endpoint).containsKey(specificationID));
	}

	/**
	 * Guard that checks if the parameters specified when the "validateMessage" operation is invoked are valid or not
	 * @param endpoint
	 * @param msg
	 * @param operation
	 * @return
	 */
	@GUARD public boolean checkParamValidity_validateMessage(String endpoint, WSMsgInfo msg, IOperation operation, OpFeedbackParam<? super Object> res){
		return (wallet.containsKey(endpoint) && FrameworkIDValidator.getInstance().validateSpecification(operation.getOperationID()));
	}

	@GUARD public boolean checkParamValidity_validateMessage(String endpoint, WSMsgInfo msg, IOperation operation, DataContainer opData, OpFeedbackParam<? super Object> res){
		return (wallet.containsKey(endpoint) && FrameworkIDValidator.getInstance().validateSpecification(operation.getOperationID()));
	}
}

